type p = Promise<'peng'>;

type GetValueType<P> = P extends Promise<infer Value> ? Value : never;

type res6 = GetValueType<p>;

// 匹配 第一个 数组

type arr = [1, 2, 3];

type GetFirst<Arr extends unknown[]> = Arr extends [infer First, ...unknown[]]
  ? First
  : never;

type res7 = GetFirst<arr>;
type res8 = GetFirst<[]>;

/**
 * any 和 unknown 的区别：any 和 unkown 都代表任意类型，但是unknown只能接收
 * 任意类型，而any除了可以接收任意类型的值，也可以赋值给任意类型（除了never）
 * 类型体操中常用 unknown 接受和匹配任何类型 而很少把任何类型赋值给某个类型变量
 */

// 匹配 最后一个 数组

type GetLast<Arr extends unknown[]> = Arr extends [...unknown[], infer Last]
  ? Last
  : never;

type res9 = GetLast<[1, 2, 3]>;

// 删除数组最后一位

type PopArr<Arr extends unknown[]> = Arr extends []
  ? []
  : Arr extends [...infer Rest, unknown]
  ? Rest
  : never;

type res10 = PopArr<[1, 2, 3]>;

// 删除数组第一位
type ShiftArr<Arr extends unknown[]> = Arr extends []
  ? []
  : Arr extends [unknown, ...infer Rest]
  ? Rest
  : never;

type res11 = ShiftArr<[1, 2, 3]>;

// 数组首位添加一位
type UnshiftArr<Arr extends unknown[], T> = Arr extends []
  ? []
  : Arr extends [...infer Rest]
  ? [T, ...Rest]
  : never;

type res19 = UnshiftArr<[123, 1], 1>;

// 数组末尾添加一位
type PushArr<Arr extends unknown[], T> = Arr extends []
  ? []
  : Arr extends [...infer Rest]
  ? [...Rest, T]
  : never;

type res20 = PushArr<[1, 2, 3], 4>;

// 匹配字符串

type StartWith<
  Str extends string,
  Prefix extends string
> = Str extends `${Prefix}${string}` ? true : false;

type res12 = StartWith<'peng and niu', 'peng'>;

// 匹配字符串 提取想要部分  再构成一个新的类型
type Replace<
  Str extends string,
  From extends string,
  To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
  ? `${Prefix}${To}${Suffix}`
  : Str;

type res13 = Replace<'peng and ?', '?', 'niu'>;

// 能够匹配和替换字符串，那也就能实现去掉空白字符的 Trim
// trim 右边
type TrimStrRight<Str extends string> = Str extends `${infer Rest}${
  | ' '
  | '\n'
  | '\t'}`
  ? TrimStrRight<Rest>
  : Str;

type res14 = TrimStrRight<'peng66   '>;

// trim 左边
type TrimStrLeft<Str extends string> = Str extends `${
  | ' '
  | '\n'
  | '\t'}${infer Rest}`
  ? TrimStrLeft<Rest>
  : Str;

type res15 = TrimStrLeft<'    peng'>;

// 最终实现trim
type Trim<Str extends string> = TrimStrRight<TrimStrLeft<Str>>;

type res16 = Trim<'   peng  '>;

// 函数
// 函数类型可以通过模式匹配来提取参数的类型;

type GetParameters<Func extends Function> = Func extends (
  ...args: infer Args
) => unknown
  ? Args
  : never;

/**
 * 类型参数 Func 是要匹配的函数类型，通过 extends 约束为 Function
 * Func 和模式类型做匹配，参数类型放到用 infer 声明的局部变量 Args 里，
 * 返回值可以是任何类型，用 unknown。
 */

type res17 = GetParameters<(name: string, age: number) => string>;

// 能提取参数类型，同样也可以提取返回值类型
type GetReturnType<Func extends Function> = Func extends (
  ...args: any[]
) => infer ReturnType
  ? ReturnType
  : never;

/**
 * 需要注意的是
 * Func 和 模式类型做匹配 提前返回值的通过 infer 声明的局部变量 ReturnType 里返回
 * 参数类型可以是任意类型 也就是any[]
 * 这里不能用unkonow，这里的解释涉及到参数的逆变性质 具体会在后面分析
 */

type res18 = GetReturnType<(name: string, age: number) => string>;

// GetThisParameterType
// 方法里可以调用 this
class Dong {
  name: string;

  constructor() {
    this.name = 'dong';
  }

  hello() {
    return "hello, I'm " + this.name;
  }
}

const dong = new Dong();
dong.hello();

dong.hello.call({ xxx: 1 });
// call 调用的时候，this 就变了，但这里却没有被检查出来 this 指向的错误

/**
 * 类型参数 T 是待处理的类型
 * 用T匹配一个模式 提取this的类型到infer 声明的局部变量 ThisType 中,
 * 其余参数是任意类型 也就是any 返回值也是任意类型
 * 返回提取到的ThisType 这样就可以提取出 this
 *
 */
type GetThisParameterType<T> = T extends (
  this: infer ThisType,
  ...args: any[]
) => any
  ? ThisType
  : unknown;

type GetThisParameterTypeRes = GetThisParameterType<typeof dong.hello>;

// 构造器
// 构造器和函数的区别是，构造器是用于创建对象的，所以可以被 new。

// 同样，我们也可以通过模式匹配提取构造器的参数和返回值的类型
interface Person {
  name: string;
}

interface PersonConstructor {
  new (name: string): Person;
}

type GetInstanceType<ConstructorType extends new (...args: any) => any> =
  ConstructorType extends new (...args: any) => infer Instance ? Instance : any;

/**
 * 用ConstructorType 匹配一个模式类型 提取返回的实例类型到infer 声明的局部变量 Instance 里面
 * 存在返回Instance
 */

type GetInstanceTypeRes = GetInstanceType<PersonConstructor>;

// 提取构造器的参数类型
/**
 * 用 Constructor 匹配一个模式 提取返回的参数类型到 infer 声明的局部变量  ParametersType
 * 存在 返回 ParametersType
 */
type GetConstructorParameters<Constructor extends new (...args: any) => any> =
  Constructor extends new (...args: infer ParametersType) => any
    ? ParametersType
    : never;

type GetConstructorParametersRes = GetConstructorParameters<PersonConstructor>;

// 索引类型
// 我们同样通过模式匹配的方式提取 ref 的值的类型
/**
 * 通过 keyof Props 取出 Props 的所用所有索引构成的联合类型 判断 ref 是否在其中
 * 也就是 ref extends keyof Props
 * 没有对应的索引 Obj[key] ts3.0 返回的是{} 而不是 never 所以在下面做了兼容处理
 * 如果有ref 这个索引的组啊 就通过infer 提取通过Value 的类型返回 否则返回 never
 */
type GetRefProps<Props> = 'ref' extends keyof Props
  ? Props extends { ref?: infer Value | undefined }
    ? Value
    : never
  : never;

type GetRefPropsRes = GetRefProps<{ ref?: undefined; name: 'peng' }>;
type GetRefPropsRes1 = GetRefProps<{ ref?: 1; name: 'peng' }>;

/**
 * 总结
 * typescript 类型的模式 匹配时通过类型 extends 一个模式类型 把需要提取的部分放到通过infer声明的局部变量李
 * 后面可以从这个局部变量拿到类型做各种后续处理
 * 模式匹配的套路 数组、字符串、函数、构造器、索引类型、Promise ... 等类型中都有大量的应用
 * 掌握号这个套路能提升很大一截类型体操水平
 */
